﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ESRI.ArcGIS.Geometry;

namespace Bes.Swsp.ToolBox
{
  /// <summary>
  /// A class representing a node on the storm drainage network.
  /// </summary>
  public class NodePoint : Element
  {
    private double? _effluentConcentration;

    public NodePoint()
      : base()
    {
      UpstreamConveyances = new HashSet<Conveyance>();
    }

    /// <summary>
    /// The unique id for this NodePoint, for example the EID of the junction feature representing this node.
    /// </summary>
    /// <param name="id">The id for the NodePoint</param>
    public NodePoint(int id)
      : base(id)
    {
      UpstreamConveyances = new HashSet<Conveyance>();
    }

    /// <summary>
    /// Gets the type of this node.
    /// </summary>
    public virtual NodeType Type
    {
      get
      {
        return NodeType.Node;
      }
    }

    /// <summary>
    /// The location of this node.
    /// </summary>
    public IPoint Point { get; set; }

    /// <summary>
    /// The conveyance that carries water downstream from this node.
    /// </summary>
    public Conveyance DownstreamConveyance { get; set; }

    /// <summary>
    /// The conveyances that bring water to this node.
    /// </summary>
    public ICollection<Conveyance> UpstreamConveyances { get; private set; }

    public NodePoint DownstreamNode
    {
      get
      {
        if (this.DownstreamConveyance != null)
        {
          return this.DownstreamConveyance.DownstreamNode;
        }
        else
        {
          return null;
        }
      }
    }

    public ICollection<NodePoint> UpstreamNodes
    {
      get
      {
        List<NodePoint> upstreamNodes = 
          (from conveyance in this.UpstreamConveyances 
           where conveyance != null && conveyance.UpstreamNode != null 
           select conveyance.UpstreamNode).ToList();
        return upstreamNodes.AsReadOnly();
      }
    }

    /// <summary>
    /// Gets or sets the total accumulated runoff flowing into this node from the upstream network.
    /// </summary>
    public double TotalAccumulatedFlow { get; set; }

    /// <summary>
    /// Gets or sets the ratio of volume reduction for flow passing through this node.
    /// </summary>
    /// <remarks>This number is the portion of the TotalAccumulatedFlow that is removed.</remarks>
    public double VolumeReduction { get; set; }

    /// <summary>
    /// Gets the total flow release from this node downstream (takes volume reduction into account)
    /// </summary>
    public double ReleasedFlow
    {
      get
      {
        return TotalAccumulatedFlow * (1 - VolumeReduction);
      }
    }

    /// <summary>
    /// Gets or sets the total accumulated load flowing into this node from the upstream network
    /// </summary>
    public double TotalAccumulatedLoad { get; set; }

    /// <summary>
    /// Gets or sets the effluent concentration for this node. Affects the ReleasedLoad.
    /// </summary>
    public double EffluentConcentration 
    {
      get
      {
        if (_effluentConcentration.HasValue)
        {
          return _effluentConcentration.Value;
        }
        else
        {
          return TotalAccumulatedFlow == 0.0 ? 0.0 : TotalAccumulatedLoad / TotalAccumulatedFlow;
        }
      }
      set
      {
        _effluentConcentration = value;
      }
    }

    public void ResetEffluentConcentration()
    {
      _effluentConcentration = null;
    }

    /// <summary>
    /// Gets the total load released from this node downstream (takes effluent concentration and/or volume reduction into account)
    /// </summary>
    public double ReleasedLoad
    {
      get
      {
        double releasedLoad = ReleasedFlow * EffluentConcentration;
        return releasedLoad < TotalAccumulatedLoad ? releasedLoad : TotalAccumulatedLoad;
      }
    }

    /// <summary>
    /// Gets a value indicating whether this node receives any local surface flow.
    /// </summary>
    /// <remarks>Any node on the surface should return true.</remarks>
    public virtual bool HasSurfaceInflow
    {
      get
      {
        // Outlets and disabled nodes do not receive flow (directly from surface)
        if (!this.Enabled || this is OutletNode)
          return false;

        // Inlets always receive flow from the surface, even if they are orphans
        if (this is InletNode)
          return true;

        // If a regular node and downstream is surface flow, then this is the upstream end or middle of a natural surface channel.
        if (this.DownstreamConveyance != null && !this.DownstreamConveyance.IsSubSurface)
          return true;

        // If any upstream conveyance is surface flow, then by definition this node receives surface flow.
        foreach (Conveyance upConveyance in this.UpstreamConveyances)
        {
          if (!upConveyance.IsSubSurface)
            return true;
        }

        // All other cases do not involve surface flow
        return false;
      }
    }

    /// <summary>
    /// Returns true if this node is not linked to any others.
    /// </summary>
    public bool IsOrphan
    {
      get
      {
        if (this.DownstreamNode != null || (this.UpstreamNodes != null && this.UpstreamNodes.Count > 0))
        {
          return false;
        }
        else
        {
          return true;
        }
      }
    }

    /// <summary>
    /// Returns true if this node has divergent flow. Call DetectErrors() on StormDrainNetwork to update this value.
    /// </summary>
    /// <remarks>A node has divergent flow if more than one node lists it as an upstream node.</remarks>
    public bool HasDivergentFlow
    {
      get
      {
        return (_errors & NetworkErrors.Divergent) == NetworkErrors.Divergent;
      }
    }

    /// <summary>
    /// A flag to determine if this node has been traversed during a tracing operation.
    /// </summary>
    /// <remarks>This should always be set to false, except during a tracing operation.
    /// It should always be reset once tracing is complete.</remarks>
    internal bool Traversed { get; set; }

    public override string ToString()
    {
      return string.Format("Node {0}", this.ID);
    }
  }
}
